package com.abu.crms.service.impl;


import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.abu.crms.vo.UserVO;
import com.abu.common.Resp;
import com.abu.common.exception.CRException;
import com.abu.crms.client.ClassesClient;
import com.abu.crms.config.LoginInterceptor;
import com.abu.crms.mapper.UserMapper;
import com.abu.crms.pojo.User;
import com.abu.crms.service.UserService;
import com.abu.crms.template.UploadTemplate;
import com.abu.crms.utils.JwtProperties;
import com.abu.crms.utils.JwtUtils;
import com.abu.crms.vo.UserInfo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * <p>
 * 普通用户表 服务实现类
 * </p>
 *
 * @author 阿布
 * @since 2021-02-03
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private UploadTemplate uploadTemplate;

    @Autowired
    private ClassesClient classesClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final String DEFAULTIMG = "https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png";

    @Autowired
    private UserMapper userMapper;

    private static final String PHONE_PREFIX = "REGISTER.CODE.";
    private static final String REGISTER_PHONE = "PHONE_LOGIN.CODE.";

    //    邮箱
    private static final String CODE_EXCHANGE = "REGISTER_CODE_EXCHANGE";
    private static final String CODE_ROUTING = "CODE_EMAIL";

    //    手机号
    private static final String PHONE_CODE_EXCHANGE = "PHONE_CODE_EXCHANGE";
    private static final String PHONE_CODE_ROUTING = "CODE_PHONE";

    //    密码忘记
    private static final String PHONE_CODE_FORGET_EXCHANGE = "PHONE_CODE_FORGET_EXCHANGE";
    private static final String PHONE_CODE_FORGET_ROUTING = "PHONE_CODE_FORGET_ROUTING";

    //    忘记密码的key缓存前缀
    private static final String FORGET_PREFIX = "SECORT_FORGET:";

    @Override
    public Boolean sendCode(String email) {
//        先检查是否已经发送过验证码
        String dbCode = this.stringRedisTemplate.opsForValue().get(PHONE_PREFIX + email);
        if (StrUtil.isNotBlank(dbCode)) {
            throw new CRException("邮件已经发送过了，无需再次发送");
        }
//        异步发送验证码
        try {
            this.rabbitTemplate.convertAndSend(CODE_EXCHANGE, CODE_ROUTING, email);
        } catch (AmqpException e) {
            return false;
        }
        return true;
    }

    @Override
    public Boolean saveUser(User user, String code) {
//        获取信息
        String cardNum = user.getCardNum();
        String userEmail = user.getUserEmail();
        String password = user.getPassword();
//        注册时添加一个默认头像
//        查询用户是否已经保存
        User dbUser = this.getOne(new QueryWrapper<User>().eq("card_num", cardNum));
        if (dbUser != null) {
            throw new CRException("学号已经存在了");
        }

        User userByTel = this.getOne(new QueryWrapper<User>().eq("user_tel", user.getUserTel()));
        if (userByTel != null) {
            throw new CRException("手机号已绑定，请更换手机号");
        }

//        检验验证码是否一致
        String dbCodeStr = this.stringRedisTemplate.opsForValue().get(PHONE_PREFIX + userEmail);
        if (!StringUtils.equals(dbCodeStr, code)) {
            throw new CRException("邮箱验证失败");
        }
//        对密码进行加盐加密
        String salt = IdUtil.simpleUUID().substring(0, 5);
        String encryptPassword = SecureUtil.md5(password + salt);
        user
                .setPassword(encryptPassword)
                .setSalt(salt)
                .setImg(DEFAULTIMG);
//        用户保存
        boolean flagMysql = this.save(user);
        boolean flagRedis = this.stringRedisTemplate.delete(PHONE_PREFIX + userEmail);
//        删除验证码
        return flagMysql && flagRedis;
    }

    @Override
    public User queryUserInfo(String cardNum, String password) {
//        1.根据用户名查询用户信息
        User dbUser = this.getOne(new QueryWrapper<User>()
                .eq("card_num", cardNum));
        if (dbUser == null) {
            throw new CRException("用户不存在，请先注册", 404);
        }
//        2.获取盐值
        String salt = dbUser.getSalt();
        String dbUserPassword = dbUser.getPassword();
//        3.对用户输入密码进行加盐加密
        String encryptPassword = SecureUtil.md5(password + salt);
//        4.比对密码是否一致
        if (!StringUtils.equals(dbUserPassword, encryptPassword)) {
            throw new CRException("用户名或密码有误", 0);
        }
        return dbUser;
    }

    @Override
    public Boolean uploadAvator(MultipartFile avatar) {
        String avatarUrl;
        try {
            avatarUrl = this.uploadTemplate.toUpload(avatar);
        } catch (IOException e) {
            throw new CRException("头像上传失败");
        }
        User user = new User();
        UserInfo userInfo = LoginInterceptor.getUserInfo();
        Long userId = userInfo.getUserId();
        user.setUserId(userId).setImg(avatarUrl);
        int count = this.userMapper.updateById(user);
//        获取用户Id
        return count == 1;
    }

    @Override
    public Boolean sendCodeToPhone(String phone) {
        String fromDbCode = this.stringRedisTemplate.opsForValue().get(REGISTER_PHONE + phone);
        if (StringUtils.isNotBlank(fromDbCode)) {
            throw new CRException("验证码发送过了，5分钟有效");
        }
        try {
            this.rabbitTemplate.convertAndSend(PHONE_CODE_EXCHANGE, PHONE_CODE_ROUTING, phone);
        } catch (AmqpException e) {
            return false;
        }
        return true;
    }

    @Override
    public String loginUser(String phone, String code) {
        //判断验证码是否为空
        if (StringUtils.isBlank(code)) {
            throw new CRException("验证码不能为空");
        }
        //判断缓存是否已经过期是否为空

        String fromDbCode = this.stringRedisTemplate.opsForValue().get(REGISTER_PHONE + phone);
        if (StringUtils.isBlank(fromDbCode)) {
            throw new CRException("验证码已失效，请重新发送");
        }
        //比对验证码
        if (!StringUtils.equals(code, fromDbCode)) {
            throw new CRException("验证码不对，请重新输入");
        }
//        手机号是否已存在

        User user = this.userMapper.selectOne(new QueryWrapper<User>().eq("user_tel", phone));
        if (user != null) {
            String username = user.getUsername();
            return this.getToken(user.getUserId(), username);
        }
        User newUser = new User();
        newUser.setUserTel(phone).setImg(DEFAULTIMG);
        //        用户新增
        int count = this.userMapper.insert(newUser);
//        删掉redis中的code
        Boolean flag = this.stringRedisTemplate.delete(REGISTER_PHONE + phone);
        if (count != 1) {
            throw new CRException("添加失败");
        }
        //        获取id
        User queriedUser = this.userMapper.selectOne(new QueryWrapper<User>().eq("user_tel", phone).select("user_id"));
        Long userId = queriedUser.getUserId();
        String username = "未知用户名";
//        生成token
        return this.getToken(userId, username);
    }

    @Override
    @Transactional
    public Boolean updateUser(User user, String password) {
        Long userId = LoginInterceptor.getUserInfo().getUserId();
        user.setUserId(userId);

//       根据手机号进行查询
        User userFromDBByPhone = this.userMapper.selectOne(new QueryWrapper<User>().eq("user_tel", user.getUserTel()));

//       如果用户存在
        if (userFromDBByPhone != null) {
            throw new CRException("手机号已被绑定，请更换手机号");
        }

//        根据学号进行查询
        User userFromDBByCardNum = this.userMapper.selectOne(new QueryWrapper<User>().eq("card_num", user.getCardNum()));
        if (userFromDBByCardNum != null && StringUtils.equals(user.getCardNum(), userFromDBByCardNum.getCardNum())) {
            throw new CRException("学号已被绑定，请更换");
        }

//        生成盐值
        String salt = IdUtil.simpleUUID().substring(0, 5);
//        加密
        String encryptPassword = SecureUtil.md5(password + salt);
        user.setSalt(salt).setPassword(encryptPassword);

        int count = this.userMapper.updateById(user);
        return count == 1;
    }

    @Override
    public UserVO queryUserInfoDetails() {
        UserInfo userInfo = LoginInterceptor.getUserInfo();
        Long userId = userInfo.getUserId();
        User user = this.userMapper.selectOne(new QueryWrapper<User>().eq("user_id", userId).select("card_num", "username", "user_email", "user_tel", "user_sex", "classes_id", "gmt_create"));
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
//        获取班级信息
        String classesId = user.getClassesId();
        if (StringUtils.isNotBlank(classesId)) {
            Resp resp = this.classesClient.queryClassNameByClassId(Long.parseLong(classesId));
            Integer code = resp.getCode();
            if (code == 1) {
                LinkedHashMap classMapResult = (LinkedHashMap) resp.getData().get("classes");
                String className = (String) classMapResult.get("className");
                userVO.setClassName(className);
            }
        }
        return userVO;
    }

    @Override
    public Boolean sendCodeToEmailOrPhone(String type, String sendTo) {
//        1.根据手机号或者邮箱判断有无相应的用户
        String userId = this.userMapper.selectByPhoneOrEmail(sendTo);
        if (StringUtils.isBlank(userId)) {
            throw new CRException("未查询到该用户");
        }
//        2.判断发送验证的类型(1:手机号，2:邮箱)
        HashMap<String, String> dataMap = new HashMap<>(2);
        dataMap.put("type", type);
        dataMap.put("sendTo", sendTo);
        this.rabbitTemplate.convertAndSend(PHONE_CODE_FORGET_EXCHANGE, PHONE_CODE_FORGET_ROUTING, dataMap);
        return true;
    }

    @Override
    public Boolean resetPwd(String code, String sendTo, String pwd) {
//        1.查询缓存code是否过期
        String fromRedisCode = this.stringRedisTemplate.opsForValue().get(FORGET_PREFIX + sendTo);
//        2.判断code时否相同
        if (!StringUtils.equals(fromRedisCode, code)) {
            throw new CRException("验证码已过期");
        }
//        3.根据num查询用户是否存在
        String userId = this.userMapper.selectByPhoneOrEmail(sendTo);
//        4.获取用户信息
        if (StringUtils.isEmpty(userId)) {
            throw new CRException("找不到用户信息，请检查一下是否绑定了手机号");
        }
//        5.重新对用户密码加盐加密
//        对密码进行加盐加密
        String salt = IdUtil.simpleUUID().substring(0, 5);
        String encryptPassword = SecureUtil.md5(pwd + salt);
//        6.保存用户密码和盐值
        User user = new User();
        user
                .setSalt(salt)
                .setPassword(encryptPassword)
                .setUserId(Long.parseLong(userId));
        int count = this.userMapper.updateById(user);
//        删除缓存中的验证码
        this.stringRedisTemplate.delete(FORGET_PREFIX + sendTo);
        return count == 1;
    }

    private String getToken(Long userId, String username) {
        //        生成token
//        1.载荷内容
        Map<String, Object> tokenContent = new HashMap<>(2);
        tokenContent.put("userId", userId);
        tokenContent.put("username", username);
//        2.获取私钥
        PrivateKey privateKey = jwtProperties.getPrivateKey();
        int expireTime = jwtProperties.getExpire();
        String token = null;
        try {
            token = JwtUtils.generateToken(tokenContent, privateKey, expireTime * 16);
        } catch (Exception e) {
            log.error("生成token失败，原因为：{}", e.getMessage());
        }
        return "UserLogin" + token;
    }

}
